/*
 * Copyright 1999-2017 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package studio.raptor.sqlparser.visitor;

import static studio.raptor.sqlparser.visitor.SQLEvalVisitor.EVAL_ERROR;
import static studio.raptor.sqlparser.visitor.SQLEvalVisitor.EVAL_EXPR;
import static studio.raptor.sqlparser.visitor.SQLEvalVisitor.EVAL_VALUE;
import static studio.raptor.sqlparser.visitor.SQLEvalVisitor.EVAL_VALUE_NULL;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import studio.raptor.sqlparser.SQLUtils;
import studio.raptor.sqlparser.ScalpelRuntimeException;
import studio.raptor.sqlparser.ast.SQLExpr;
import studio.raptor.sqlparser.ast.SQLObject;
import studio.raptor.sqlparser.ast.expr.SQLBetweenExpr;
import studio.raptor.sqlparser.ast.expr.SQLBinaryExpr;
import studio.raptor.sqlparser.ast.expr.SQLBinaryOpExpr;
import studio.raptor.sqlparser.ast.expr.SQLBinaryOperator;
import studio.raptor.sqlparser.ast.expr.SQLCaseExpr;
import studio.raptor.sqlparser.ast.expr.SQLCharExpr;
import studio.raptor.sqlparser.ast.expr.SQLHexExpr;
import studio.raptor.sqlparser.ast.expr.SQLIdentifierExpr;
import studio.raptor.sqlparser.ast.expr.SQLInListExpr;
import studio.raptor.sqlparser.ast.expr.SQLMethodInvokeExpr;
import studio.raptor.sqlparser.ast.expr.SQLNullExpr;
import studio.raptor.sqlparser.ast.expr.SQLNumericLiteralExpr;
import studio.raptor.sqlparser.ast.expr.SQLQueryExpr;
import studio.raptor.sqlparser.ast.expr.SQLUnaryExpr;
import studio.raptor.sqlparser.ast.expr.SQLUnaryOperator;
import studio.raptor.sqlparser.ast.expr.SQLVariantRefExpr;
import studio.raptor.sqlparser.ast.statement.SQLExprTableSource;
import studio.raptor.sqlparser.ast.statement.SQLSelect;
import studio.raptor.sqlparser.ast.statement.SQLSelectItem;
import studio.raptor.sqlparser.ast.statement.SQLSelectQueryBlock;
import studio.raptor.sqlparser.dialect.db2.visitor.DB2EvalVisitor;
import studio.raptor.sqlparser.dialect.mysql.visitor.MySqlEvalVisitorImpl;
import studio.raptor.sqlparser.dialect.oracle.visitor.OracleEvalVisitor;
import studio.raptor.sqlparser.dialect.postgresql.visitor.PGEvalVisitor;
import studio.raptor.sqlparser.dialect.sqlserver.visitor.SQLServerEvalVisitor;
import studio.raptor.sqlparser.util.HexBin;
import studio.raptor.sqlparser.util.JdbcConstants;
import studio.raptor.sqlparser.util.JdbcUtils;
import studio.raptor.sqlparser.util.Utils;
import studio.raptor.sqlparser.visitor.functions.Ascii;
import studio.raptor.sqlparser.visitor.functions.Bin;
import studio.raptor.sqlparser.visitor.functions.BitLength;
import studio.raptor.sqlparser.visitor.functions.Char;
import studio.raptor.sqlparser.visitor.functions.Concat;
import studio.raptor.sqlparser.visitor.functions.Elt;
import studio.raptor.sqlparser.visitor.functions.Function;
import studio.raptor.sqlparser.visitor.functions.Greatest;
import studio.raptor.sqlparser.visitor.functions.Hex;
import studio.raptor.sqlparser.visitor.functions.If;
import studio.raptor.sqlparser.visitor.functions.Insert;
import studio.raptor.sqlparser.visitor.functions.Instr;
import studio.raptor.sqlparser.visitor.functions.Isnull;
import studio.raptor.sqlparser.visitor.functions.Lcase;
import studio.raptor.sqlparser.visitor.functions.Least;
import studio.raptor.sqlparser.visitor.functions.Left;
import studio.raptor.sqlparser.visitor.functions.Length;
import studio.raptor.sqlparser.visitor.functions.Locate;
import studio.raptor.sqlparser.visitor.functions.Lpad;
import studio.raptor.sqlparser.visitor.functions.Ltrim;
import studio.raptor.sqlparser.visitor.functions.Now;
import studio.raptor.sqlparser.visitor.functions.OneParamFunctions;
import studio.raptor.sqlparser.visitor.functions.Reverse;
import studio.raptor.sqlparser.visitor.functions.Right;
import studio.raptor.sqlparser.visitor.functions.Substring;
import studio.raptor.sqlparser.visitor.functions.Trim;
import studio.raptor.sqlparser.visitor.functions.Ucase;
import studio.raptor.sqlparser.visitor.functions.Unhex;
import studio.raptor.sqlparser.wall.spi.WallVisitorUtils;
import studio.raptor.sqlparser.wall.spi.WallVisitorUtils.WallConditionContext;

public class SQLEvalVisitorUtils {

  private static Map<String, Function> functions = new HashMap<String, Function>();

  static {
    registerBaseFunctions();
  }

  public static Object evalExpr(String dbType, String expr, Object... parameters) {
    SQLExpr sqlExpr = SQLUtils.toSQLExpr(expr, dbType);
    return eval(dbType, sqlExpr, parameters);
  }

  public static Object evalExpr(String dbType, String expr, List<Object> parameters) {
    SQLExpr sqlExpr = SQLUtils.toSQLExpr(expr);
    return eval(dbType, sqlExpr, parameters);
  }

  public static Object eval(String dbType, SQLObject sqlObject, Object... parameters) {
    Object value = eval(dbType, sqlObject, Arrays.asList(parameters));

    if (value == EVAL_VALUE_NULL) {
      value = null;
    }

    return value;
  }

  public static Object getValue(SQLObject sqlObject) {
    if (sqlObject instanceof SQLNumericLiteralExpr) {
      return ((SQLNumericLiteralExpr) sqlObject).getNumber();
    }

    return sqlObject.getAttributes().get(EVAL_VALUE);
  }

  public static Object eval(String dbType, SQLObject sqlObject, List<Object> parameters) {
    return eval(dbType, sqlObject, parameters, true);
  }

  public static Object eval(String dbType, SQLObject sqlObject, List<Object> parameters,
      boolean throwError) {
    SQLEvalVisitor visitor = createEvalVisitor(dbType);
    visitor.setParameters(parameters);
    sqlObject.accept(visitor);

    Object value = getValue(sqlObject);
    if (value == null) {
      if (throwError && !sqlObject.getAttributes().containsKey(EVAL_VALUE)) {
        throw new ScalpelRuntimeException(
            "eval error : " + SQLUtils.toSQLString(sqlObject, dbType));
      }
    }

    return value;
  }

  public static SQLEvalVisitor createEvalVisitor(String dbType) {
    if (JdbcUtils.MYSQL.equalsIgnoreCase(dbType)) {
      return new MySqlEvalVisitorImpl();
    }

    if (JdbcUtils.MARIADB.equalsIgnoreCase(dbType)) {
      return new MySqlEvalVisitorImpl();
    }

    if (JdbcUtils.H2.equalsIgnoreCase(dbType)) {
      return new MySqlEvalVisitorImpl();
    }

    if (JdbcUtils.ORACLE.equalsIgnoreCase(dbType) || JdbcUtils.ALI_ORACLE.equalsIgnoreCase(dbType)) {
      return new OracleEvalVisitor();
    }

    if (JdbcConstants.POSTGRESQL.equalsIgnoreCase(dbType)
        || JdbcConstants.ENTERPRISEDB.equalsIgnoreCase(dbType)) {
      return new PGEvalVisitor();
    }

    if (JdbcUtils.SQL_SERVER.equalsIgnoreCase(dbType) || JdbcUtils.JTDS.equalsIgnoreCase(dbType)) {
      return new SQLServerEvalVisitor();
    }

    if (JdbcUtils.DB2.equalsIgnoreCase(dbType)) {
      return new DB2EvalVisitor();
    }

    return new SQLEvalVisitorImpl();
  }

  static void registerBaseFunctions() {
    functions.put("now", Now.instance);
    functions.put("concat", Concat.instance);
    functions.put("concat_ws", Concat.instance);
    functions.put("ascii", Ascii.instance);
    functions.put("bin", Bin.instance);
    functions.put("bit_length", BitLength.instance);
    functions.put("insert", Insert.instance);
    functions.put("instr", Instr.instance);
    functions.put("char", Char.instance);
    functions.put("elt", Elt.instance);
    functions.put("left", Left.instance);
    functions.put("locate", Locate.instance);
    functions.put("lpad", Lpad.instance);
    functions.put("ltrim", Ltrim.instance);
    functions.put("mid", Substring.instance);
    functions.put("substr", Substring.instance);
    functions.put("substring", Substring.instance);
    functions.put("right", Right.instance);
    functions.put("reverse", Reverse.instance);
    functions.put("len", Length.instance);
    functions.put("length", Length.instance);
    functions.put("char_length", Length.instance);
    functions.put("character_length", Length.instance);
    functions.put("trim", Trim.instance);
    functions.put("ucase", Ucase.instance);
    functions.put("upper", Ucase.instance);
    functions.put("lcase", Lcase.instance);
    functions.put("lower", Lcase.instance);
    functions.put("hex", Hex.instance);
    functions.put("unhex", Unhex.instance);
    functions.put("greatest", Greatest.instance);
    functions.put("least", Least.instance);
    functions.put("isnull", Isnull.instance);
    functions.put("if", If.instance);

    functions.put("md5", OneParamFunctions.instance);
    functions.put("bit_count", OneParamFunctions.instance);
    functions.put("soundex", OneParamFunctions.instance);
    functions.put("space", OneParamFunctions.instance);
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLMethodInvokeExpr x) {
    String methodName = x.getMethodName().toLowerCase();

    Function function = visitor.getFunction(methodName);

    if (function == null) {
      function = functions.get(methodName);
    }

    if (function != null) {
      Object result = function.eval(visitor, x);

      if (result != SQLEvalVisitor.EVAL_ERROR) {
        x.getAttributes().put(EVAL_VALUE, result);
      }
      return false;
    }

    if ("mod".equals(methodName)) {
      if (x.getParameters().size() != 2) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      SQLExpr param1 = x.getParameters().get(1);
      param0.accept(visitor);
      param1.accept(visitor);

      Object param0Value = param0.getAttributes().get(EVAL_VALUE);
      Object param1Value = param1.getAttributes().get(EVAL_VALUE);
      if (param0Value == null || param1Value == null) {
        return false;
      }

      long intValue0 = castToLong(param0Value);
      long intValue1 = castToLong(param1Value);

      long result = intValue0 % intValue1;
      if (result >= Integer.MIN_VALUE && result <= Integer.MAX_VALUE) {
        int intResult = (int) result;
        x.putAttribute(EVAL_VALUE, intResult);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("abs".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      Object result;
      if (paramValue instanceof Integer) {
        result = Math.abs(((Integer) paramValue).intValue());
      } else if (paramValue instanceof Long) {
        result = Math.abs(((Long) paramValue).longValue());
      } else {
        result = castToDecimal(paramValue).abs();
      }

      x.putAttribute(EVAL_VALUE, result);
    } else if ("acos".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      double doubleValue = castToDouble(paramValue);
      double result = Math.acos(doubleValue);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("asin".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      double doubleValue = castToDouble(paramValue);
      double result = Math.asin(doubleValue);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("atan".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      double doubleValue = castToDouble(paramValue);
      double result = Math.atan(doubleValue);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("atan2".equals(methodName)) {
      if (x.getParameters().size() != 2) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      SQLExpr param1 = x.getParameters().get(1);
      param0.accept(visitor);
      param1.accept(visitor);

      Object param0Value = param0.getAttributes().get(EVAL_VALUE);
      Object param1Value = param1.getAttributes().get(EVAL_VALUE);
      if (param0Value == null || param1Value == null) {
        return false;
      }

      double doubleValue0 = castToDouble(param0Value);
      double doubleValue1 = castToDouble(param1Value);
      double result = Math.atan2(doubleValue0, doubleValue1);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("ceil".equals(methodName) || "ceiling".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      double doubleValue = castToDouble(paramValue);
      int result = (int) Math.ceil(doubleValue);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("cos".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      double doubleValue = castToDouble(paramValue);
      double result = Math.cos(doubleValue);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("sin".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      double doubleValue = castToDouble(paramValue);
      double result = Math.sin(doubleValue);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("log".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      double doubleValue = castToDouble(paramValue);
      double result = Math.log(doubleValue);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("log10".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      double doubleValue = castToDouble(paramValue);
      double result = Math.log10(doubleValue);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("tan".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      double doubleValue = castToDouble(paramValue);
      double result = Math.tan(doubleValue);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("sqrt".equals(methodName)) {
      if (x.getParameters().size() != 1) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      param0.accept(visitor);

      Object paramValue = param0.getAttributes().get(EVAL_VALUE);
      if (paramValue == null) {
        return false;
      }

      double doubleValue = castToDouble(paramValue);
      double result = Math.sqrt(doubleValue);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("power".equals(methodName) || "pow".equals(methodName)) {
      if (x.getParameters().size() != 2) {
        return false;
      }

      SQLExpr param0 = x.getParameters().get(0);
      SQLExpr param1 = x.getParameters().get(1);
      param0.accept(visitor);
      param1.accept(visitor);

      Object param0Value = param0.getAttributes().get(EVAL_VALUE);
      Object param1Value = param1.getAttributes().get(EVAL_VALUE);
      if (param0Value == null || param1Value == null) {
        return false;
      }

      double doubleValue0 = castToDouble(param0Value);
      double doubleValue1 = castToDouble(param1Value);
      double result = Math.pow(doubleValue0, doubleValue1);

      if (Double.isNaN(result)) {
        x.putAttribute(EVAL_VALUE, null);
      } else {
        x.putAttribute(EVAL_VALUE, result);
      }
    } else if ("pi".equals(methodName)) {
      x.putAttribute(EVAL_VALUE, Math.PI);
    } else if ("rand".equals(methodName)) {
      x.putAttribute(EVAL_VALUE, Math.random());
    } else if ("chr".equals(methodName) && x.getParameters().size() == 1) {
      SQLExpr first = x.getParameters().get(0);
      Object firstResult = getValue(first);
      if (firstResult instanceof Number) {
        int intValue = ((Number) firstResult).intValue();
        char ch = (char) intValue;
        x.putAttribute(EVAL_VALUE, Character.toString(ch));
      }
    } else if ("current_user".equals(methodName)) {
      x.putAttribute(EVAL_VALUE, "CURRENT_USER");
    }
    return false;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLCharExpr x) {
    x.putAttribute(EVAL_VALUE, x.getText());
    return true;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLHexExpr x) {
    String hex = x.getHex();
    byte[] bytes = HexBin.decode(hex);
    if (bytes == null) {
      x.putAttribute(EVAL_VALUE, EVAL_ERROR);
    } else {
      String val = new String(bytes);
      x.putAttribute(EVAL_VALUE, val);
    }
    return true;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLBinaryExpr x) {
    String text = x.getValue();

    long[] words = new long[text.length() / 64 + 1];
    for (int i = 0; i < text.length(); ++i) {
      char ch = text.charAt(i);
      if (ch == '1') {
        int wordIndex = i >> 6;
        words[wordIndex] |= (1L << i);
      }
    }

    Object val;

    if (words.length == 1) {
      val = words[0];
    } else {
      byte[] bytes = new byte[words.length * 8];

      for (int i = 0; i < words.length; ++i) {
        Utils.putLong(bytes, i * 8, words[i]);
      }

      val = new BigInteger(bytes);
    }

    x.putAttribute(EVAL_VALUE, val);

    return false;
  }

  public static SQLExpr unwrap(SQLExpr expr) {
    if (expr == null) {
      return null;
    }

    if (expr instanceof SQLQueryExpr) {
      SQLSelect select = ((SQLQueryExpr) expr).getSubQuery();
      if (select == null) {
        return null;
      }
      if (select.getQuery() instanceof SQLSelectQueryBlock) {
        SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) select.getQuery();
        if (queryBlock.getFrom() == null) {
          if (queryBlock.getSelectList().size() == 1) {
            return queryBlock.getSelectList().get(0).getExpr();
          }
        }
      }
    }

    return expr;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLBetweenExpr x) {
    SQLExpr testExpr = unwrap(x.getTestExpr());
    testExpr.accept(visitor);

    if (!testExpr.getAttributes().containsKey(EVAL_VALUE)) {
      return false;
    }

    Object value = testExpr.getAttribute(EVAL_VALUE);

    SQLExpr beginExpr = unwrap(x.getBeginExpr());
    beginExpr.accept(visitor);
    if (!beginExpr.getAttributes().containsKey(EVAL_VALUE)) {
      return false;
    }

    Object begin = beginExpr.getAttribute(EVAL_VALUE);

    if (lt(value, begin)) {
      x.getAttributes().put(EVAL_VALUE, x.isNot() ? true : false);
      return false;
    }

    SQLExpr endExpr = unwrap(x.getEndExpr());
    endExpr.accept(visitor);
    if (!endExpr.getAttributes().containsKey(EVAL_VALUE)) {
      return false;
    }

    Object end = endExpr.getAttribute(EVAL_VALUE);

    if (gt(value, end)) {
      x.getAttributes().put(EVAL_VALUE, x.isNot() ? true : false);
      return false;
    }

    x.getAttributes().put(EVAL_VALUE, x.isNot() ? false : true);
    return false;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLNullExpr x) {
    x.getAttributes().put(EVAL_VALUE, EVAL_VALUE_NULL);
    return false;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLCaseExpr x) {
    Object value;
    if (x.getValueExpr() != null) {
      x.getValueExpr().accept(visitor);

      if (!x.getValueExpr().getAttributes().containsKey(EVAL_VALUE)) {
        return false;
      }

      value = x.getValueExpr().getAttribute(EVAL_VALUE);
    } else {
      value = null;
    }

    for (SQLCaseExpr.Item item : x.getItems()) {
      item.getConditionExpr().accept(visitor);

      if (!item.getConditionExpr().getAttributes().containsKey(EVAL_VALUE)) {
        return false;
      }

      Object conditionValue = item.getConditionExpr().getAttribute(EVAL_VALUE);

      if ((x.getValueExpr() != null && eq(value, conditionValue))
          || (x.getValueExpr() == null && conditionValue instanceof Boolean
          && (Boolean) conditionValue == Boolean.TRUE)) {
        item.getValueExpr().accept(visitor);

        if (item.getValueExpr().getAttributes().containsKey(EVAL_VALUE)) {
          x.getAttributes().put(EVAL_VALUE, item.getValueExpr().getAttribute(EVAL_VALUE));
        }

        return false;
      }
    }

    if (x.getElseExpr() != null) {
      x.getElseExpr().accept(visitor);

      if (x.getElseExpr().getAttributes().containsKey(EVAL_VALUE)) {
        x.getAttributes().put(EVAL_VALUE, x.getElseExpr().getAttribute(EVAL_VALUE));
      }
    }

    return false;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLInListExpr x) {
    SQLExpr valueExpr = x.getExpr();
    valueExpr.accept(visitor);
    if (!valueExpr.getAttributes().containsKey(EVAL_VALUE)) {
      return false;
    }
    Object value = valueExpr.getAttribute(EVAL_VALUE);

    for (SQLExpr item : x.getTargetList()) {
      item.accept(visitor);
      if (!item.getAttributes().containsKey(EVAL_VALUE)) {
        return false;
      }
      Object itemValue = item.getAttribute(EVAL_VALUE);
      if (eq(value, itemValue)) {
        x.getAttributes().put(EVAL_VALUE, x.isNot() ? false : true);
        return false;
      }
    }

    x.getAttributes().put(EVAL_VALUE, x.isNot() ? true : false);
    return false;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLQueryExpr x) {
    if (WallVisitorUtils.isSimpleCountTableSource(null, ((SQLQueryExpr) x).getSubQuery())) {
      x.putAttribute(EVAL_VALUE, 1);
      return false;
    }

    if (x.getSubQuery().getQuery() instanceof SQLSelectQueryBlock) {
      SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) x.getSubQuery().getQuery();

      boolean nullFrom = false;
      if (queryBlock.getFrom() == null) {
        nullFrom = true;
      } else if (queryBlock.getFrom() instanceof SQLExprTableSource) {
        SQLExpr expr = ((SQLExprTableSource) queryBlock.getFrom()).getExpr();
        if (expr instanceof SQLIdentifierExpr) {
          if ("dual".equalsIgnoreCase(((SQLIdentifierExpr) expr).getName())) {
            nullFrom = true;
          }
        }
      }

      if (nullFrom) {
        List<Object> row = new ArrayList<Object>(queryBlock.getSelectList().size());
        for (int i = 0; i < queryBlock.getSelectList().size(); ++i) {
          SQLSelectItem item = queryBlock.getSelectList().get(i);
          item.getExpr().accept(visitor);
          Object cell = item.getExpr().getAttribute(EVAL_VALUE);
          row.add(cell);
        }
        List<List<Object>> rows = new ArrayList<List<Object>>(1);
        rows.add(row);

        Object result = rows;
        queryBlock.putAttribute(EVAL_VALUE, result);
        x.getSubQuery().putAttribute(EVAL_VALUE, result);
        x.putAttribute(EVAL_VALUE, result);

        return false;
      }
    }

    return false;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLUnaryExpr x) {
    final WallConditionContext wallConditionContext = WallVisitorUtils.getWallConditionContext();
    if (x.getOperator() == SQLUnaryOperator.Compl && wallConditionContext != null) {
      wallConditionContext.setBitwise(true);
    }

    x.getExpr().accept(visitor);

    Object val = x.getExpr().getAttribute(EVAL_VALUE);
    if (val == EVAL_ERROR) {
      x.putAttribute(EVAL_VALUE, EVAL_ERROR);
      return false;
    }

    if (val == null) {
      x.putAttribute(EVAL_VALUE, EVAL_VALUE_NULL);
      return false;
    }

    switch (x.getOperator()) {
      case BINARY:
      case RAW:
        x.putAttribute(EVAL_VALUE, val);
        break;
      case NOT:
      case Not: {
        Boolean booleanVal = castToBoolean(val);
        if (booleanVal != null) {
          x.putAttribute(EVAL_VALUE, !booleanVal);
        }
        break;
      }
      case Plus:
        x.putAttribute(EVAL_VALUE, val);
        break;
      case Negative:
        x.putAttribute(EVAL_VALUE, multi(val, -1));
        break;
      case Compl:
        x.putAttribute(EVAL_VALUE, ~castToInteger(val));
        break;
      default:
        break;
    }

    return false;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLBinaryOpExpr x) {
    SQLExpr left = unwrap(x.getLeft());
    SQLExpr right = unwrap(x.getRight());

    // final WallConditionContext old = wallConditionContextLocal.get();

    left.accept(visitor);
    right.accept(visitor);

    final WallConditionContext wallConditionContext = WallVisitorUtils.getWallConditionContext();
    if (x.getOperator() == SQLBinaryOperator.BooleanOr) {
      if (wallConditionContext != null) {
        if (left.getAttribute(EVAL_VALUE) == Boolean.TRUE
            || right.getAttribute(EVAL_VALUE) == Boolean.TRUE) {
          wallConditionContext.setPartAlwayTrue(true);
        }
      }
    } else if (x.getOperator() == SQLBinaryOperator.BooleanAnd) {
      if (wallConditionContext != null) {
        if (left.getAttribute(EVAL_VALUE) == Boolean.FALSE
            || right.getAttribute(EVAL_VALUE) == Boolean.FALSE) {
          wallConditionContext.setPartAlwayFalse(true);
        }
      }
    } else if (x.getOperator() == SQLBinaryOperator.BooleanXor) {
      if (wallConditionContext != null) {
        wallConditionContext.setXor(true);
      }
    } else if (x.getOperator() == SQLBinaryOperator.BitwiseAnd //
        || x.getOperator() == SQLBinaryOperator.BitwiseNot //
        || x.getOperator() == SQLBinaryOperator.BitwiseOr //
        || x.getOperator() == SQLBinaryOperator.BitwiseXor) {
      if (wallConditionContext != null) {
        wallConditionContext.setBitwise(true);
      }
    }

    Object leftValue = left.getAttribute(EVAL_VALUE);
    Object rightValue = right.getAttributes().get(EVAL_VALUE);

    if (x.getOperator() == SQLBinaryOperator.Like) {
      if (isAlwayTrueLikePattern(x.getRight())) {
        x.putAttribute(WallVisitorUtils.HAS_TRUE_LIKE, Boolean.TRUE);
        x.putAttribute(EVAL_VALUE, Boolean.TRUE);
        return false;
      }
    }

    if (x.getOperator() == SQLBinaryOperator.NotLike) {
      if (isAlwayTrueLikePattern(x.getRight())) {
        x.putAttribute(EVAL_VALUE, Boolean.FALSE);
        return false;
      }
    }

    boolean leftHasValue = left.getAttributes().containsKey(EVAL_VALUE);
    boolean rightHasValue = right.getAttributes().containsKey(EVAL_VALUE);

    if ((!leftHasValue) && !rightHasValue) {
      SQLExpr leftEvalExpr = (SQLExpr) left.getAttribute(EVAL_EXPR);
      SQLExpr rightEvalExpr = (SQLExpr) right.getAttribute(EVAL_EXPR);

      if (leftEvalExpr != null && leftEvalExpr.equals(rightEvalExpr)) {
        switch (x.getOperator()) {
          case Like:
          case Equality:
          case GreaterThanOrEqual:
          case LessThanOrEqual:
          case NotLessThan:
          case NotGreaterThan:
            x.putAttribute(EVAL_VALUE, Boolean.TRUE);
            return false;
          case NotEqual:
          case NotLike:
          case GreaterThan:
          case LessThan:
            x.putAttribute(EVAL_VALUE, Boolean.FALSE);
            return false;
          default:
            break;
        }
      }
    }

    if (!leftHasValue) {
      return false;
    }

    if (!rightHasValue) {
      return false;
    }

    if (wallConditionContext != null) {
      wallConditionContext.setConstArithmetic(true);
    }

    leftValue = processValue(leftValue);
    rightValue = processValue(rightValue);

    if (leftValue == null || rightValue == null) {
      return false;
    }

    Object value = null;
    switch (x.getOperator()) {
      case Add:
        value = add(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case Subtract:
        value = sub(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case Multiply:
        value = multi(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case Divide:
        value = div(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case RightShift:
        value = rightShift(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case BitwiseAnd:
        value = bitAnd(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case BitwiseOr:
        value = bitOr(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case GreaterThan:
        value = gt(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case GreaterThanOrEqual:
        value = gteq(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case LessThan:
        value = lt(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case LessThanOrEqual:
        value = lteq(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case Is:
        if (rightValue == EVAL_VALUE_NULL) {
          if (leftValue != null) {
            value = (leftValue == EVAL_VALUE_NULL);
            x.putAttribute(EVAL_VALUE, value);
            break;
          }
        }
        break;
      case Equality:
        value = eq(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case NotEqual:
        value = !eq(leftValue, rightValue);
        x.putAttribute(EVAL_VALUE, value);
        break;
      case IsNot:
        if (leftValue == EVAL_VALUE_NULL) {
          x.putAttribute(EVAL_VALUE, false);
        } else if (leftValue != null) {
          x.putAttribute(EVAL_VALUE, true);
        }
        break;
      case RegExp:
      case RLike: {
        String pattern = castToString(rightValue);
        String input = castToString(left.getAttributes().get(EVAL_VALUE));
        boolean matchResult = Pattern.matches(pattern, input);
        x.putAttribute(EVAL_VALUE, matchResult);
        break;
      }
      case NotRegExp:
      case NotRLike: {
        String pattern = castToString(rightValue);
        String input = castToString(left.getAttributes().get(EVAL_VALUE));
        boolean matchResult = !Pattern.matches(pattern, input);
        x.putAttribute(EVAL_VALUE, matchResult);
        break;
      }
      case Like: {
        String pattern = castToString(rightValue);
        String input = castToString(left.getAttributes().get(EVAL_VALUE));
        boolean matchResult = like(input, pattern);
        x.putAttribute(EVAL_VALUE, matchResult);
        break;
      }
      case NotLike: {
        String pattern = castToString(rightValue);
        String input = castToString(left.getAttributes().get(EVAL_VALUE));
        boolean matchResult = !like(input, pattern);
        x.putAttribute(EVAL_VALUE, matchResult);
        break;
      }
      case Concat: {
        String result = leftValue.toString() + rightValue.toString();
        x.putAttribute(EVAL_VALUE, result);
        break;
      }
      case BooleanAnd: {
        boolean first = eq(leftValue, true);
        boolean second = eq(rightValue, true);
        x.putAttribute(EVAL_VALUE, first && second);
        break;
      }
      case BooleanOr: {
        boolean first = eq(leftValue, true);
        boolean second = eq(rightValue, true);
        x.putAttribute(EVAL_VALUE, first || second);
        break;
      }
      default:
        break;
    }

    return false;
  }

  @SuppressWarnings("rawtypes")
  private static Object processValue(Object value) {
    if (value instanceof List) {
      List list = (List) value;
      if (list.size() == 1) {
        return processValue(list.get(0));
      }
    } else if (value instanceof Date) {
      return ((Date) value).getTime();
    }
    return value;
  }

  private static boolean isAlwayTrueLikePattern(SQLExpr x) {
    if (x instanceof SQLCharExpr) {
      String text = ((SQLCharExpr) x).getText();

      if (text.length() > 0) {
        for (char ch : text.toCharArray()) {
          if (ch != '%') {
            return false;
          }
        }
        return true;
      }
    }
    return false;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLNumericLiteralExpr x) {
    x.getAttributes().put(EVAL_VALUE, x.getNumber());
    return false;
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLVariantRefExpr x) {
    if (!"?".equals(x.getName())) {
      return false;
    }

    Map<String, Object> attributes = x.getAttributes();

    int varIndex = x.getIndex();

    if (varIndex != -1 && visitor.getParameters().size() > varIndex) {
      boolean containsValue = attributes.containsKey(EVAL_VALUE);
      if (!containsValue) {
        Object value = visitor.getParameters().get(varIndex);
        if (value == null) {
          value = EVAL_VALUE_NULL;
        }
        attributes.put(EVAL_VALUE, value);
      }
    }

    return false;
  }

  public static Boolean castToBoolean(Object val) {
    if (val == null) {
      return null;
    }

    if (val == EVAL_VALUE_NULL) {
      return null;
    }

    if (val instanceof Boolean) {
      return (Boolean) val;
    }

    if (val instanceof Number) {
      return ((Number) val).intValue() > 0;
    }

    if (val instanceof String) {
      if ("1".equals(val) || "true".equalsIgnoreCase((String) val)) {
        return true;
      }

      return false;
    }

    throw new IllegalArgumentException(val.getClass() + " not supported.");
  }

  public static String castToString(Object val) {
    Object value = val;

    if (value == null) {
      return null;
    }

    return value.toString();
  }

  public static Byte castToByte(Object val) {
    if (val == null) {
      return null;
    }

    if (val instanceof Byte) {
      return (Byte) val;
    }

    if (val instanceof String) {
      return Byte.parseByte((String) val);
    }

    return ((Number) val).byteValue();
  }

  public static Short castToShort(Object val) {
    if (val == null || val == EVAL_VALUE_NULL) {
      return null;
    }

    if (val instanceof Short) {
      return (Short) val;
    }

    if (val instanceof String) {
      return Short.parseShort((String) val);
    }

    return ((Number) val).shortValue();
  }

  @SuppressWarnings("rawtypes")
  public static Integer castToInteger(Object val) {
    if (val == null) {
      return null;
    }

    if (val instanceof Integer) {
      return (Integer) val;
    }

    if (val instanceof String) {
      return Integer.parseInt((String) val);
    }

    if (val instanceof List) {
      List list = (List) val;
      if (list.size() == 1) {
        return castToInteger(list.get(0));
      }
    }

    if (val instanceof Boolean) {
      if (((Boolean) val).booleanValue()) {
        return 1;
      } else {
        return 0;
      }
    }

    if (val instanceof Number) {
      return ((Number) val).intValue();
    }

    throw new ScalpelRuntimeException("cast error");
  }

  @SuppressWarnings("rawtypes")
  public static Long castToLong(Object val) {
    if (val == null) {
      return null;
    }

    if (val instanceof Long) {
      return (Long) val;
    }

    if (val instanceof String) {
      return Long.parseLong((String) val);
    }

    if (val instanceof List) {
      List list = (List) val;
      if (list.size() == 1) {
        return castToLong(list.get(0));
      }
    }

    if (val instanceof Boolean) {
      if (((Boolean) val).booleanValue()) {
        return 1l;
      } else {
        return 0l;
      }
    }

    return ((Number) val).longValue();
  }

  public static Float castToFloat(Object val) {
    if (val == null || val == EVAL_VALUE_NULL) {
      return null;
    }

    if (val instanceof Float) {
      return (Float) val;
    }

    return ((Number) val).floatValue();
  }

  public static Double castToDouble(Object val) {
    if (val == null || val == EVAL_VALUE_NULL) {
      return null;
    }

    if (val instanceof Double) {
      return (Double) val;
    }

    return ((Number) val).doubleValue();
  }

  public static BigInteger castToBigInteger(Object val) {
    if (val == null) {
      return null;
    }

    if (val instanceof BigInteger) {
      return (BigInteger) val;
    }

    if (val instanceof String) {
      return new BigInteger((String) val);
    }

    return BigInteger.valueOf(((Number) val).longValue());
  }

  public static Number castToNumber(String val) {
    if (val == null) {
      return null;
    }

    try {
      return Byte.parseByte(val);
    } catch (NumberFormatException e) {
    }

    try {
      return Short.parseShort(val);
    } catch (NumberFormatException e) {
    }

    try {
      return Integer.parseInt(val);
    } catch (NumberFormatException e) {
    }

    try {
      return Long.parseLong(val);
    } catch (NumberFormatException e) {
    }

    try {
      return Float.parseFloat(val);
    } catch (NumberFormatException e) {
    }

    try {
      return Double.parseDouble(val);
    } catch (NumberFormatException e) {
    }

    try {
      return new BigInteger(val);
    } catch (NumberFormatException e) {
    }

    try {
      return new BigDecimal(val);
    } catch (NumberFormatException e) {
      return 0;
    }
  }

  public static Date castToDate(Object val) {
    if (val == null) {
      return null;
    }

    if (val instanceof Date) {
      return (Date) val;
    }

    if (val instanceof Number) {
      return new Date(((Number) val).longValue());
    }

    if (val instanceof String) {
      return castToDate((String) val);
    }

    throw new ScalpelRuntimeException("can cast to date");
  }

  public static Date castToDate(String text) {
    if (text == null || text.length() == 0) {
      return null;
    }

    String format;

    if (text.length() == "yyyy-MM-dd".length()) {
      format = "yyyy-MM-dd";
    } else {
      format = "yyyy-MM-dd HH:mm:ss";
    }

    try {
      return new SimpleDateFormat(format).parse(text);
    } catch (ParseException e) {
      throw new ScalpelRuntimeException("format : " + format + ", value : " + text, e);
    }
  }

  public static BigDecimal castToDecimal(Object val) {
    if (val == null) {
      return null;
    }

    if (val instanceof BigDecimal) {
      return (BigDecimal) val;
    }

    if (val instanceof String) {
      return new BigDecimal((String) val);
    }

    if (val instanceof Float) {
      return new BigDecimal((Float) val);
    }

    if (val instanceof Double) {
      return new BigDecimal((Double) val);
    }

    return BigDecimal.valueOf(((Number) val).longValue());
  }

  public static Object rightShift(Object a, Object b) {
    if (a == null || b == null) {
      return null;
    }

    if (a instanceof Long || b instanceof Long) {
      return castToLong(a).longValue() >> castToLong(b).longValue();
    }

    return castToInteger(a).intValue() >> castToInteger(b).intValue();
  }

  public static Object bitAnd(Object a, Object b) {
    if (a == null || b == null) {
      return null;
    }

    if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
      return null;
    }

    if (a instanceof String) {
      a = castToNumber((String) a);
    }

    if (b instanceof String) {
      b = castToNumber((String) b);
    }

    if (a instanceof Long || b instanceof Long) {
      return castToLong(a).longValue() & castToLong(b).longValue();
    }

    return castToInteger(a).intValue() & castToInteger(b).intValue();
  }

  public static Object bitOr(Object a, Object b) {
    if (a == null || b == null) {
      return null;
    }

    if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
      return null;
    }

    if (a instanceof String) {
      a = castToNumber((String) a);
    }

    if (b instanceof String) {
      b = castToNumber((String) b);
    }

    if (a instanceof Long || b instanceof Long) {
      return castToLong(a).longValue() | castToLong(b).longValue();
    }

    return castToInteger(a).intValue() | castToInteger(b).intValue();
  }

  public static Object div(Object a, Object b) {
    if (a == null || b == null) {
      return null;
    }

    if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
      return null;
    }

    if (a instanceof String) {
      a = castToNumber((String) a);
    }

    if (b instanceof String) {
      b = castToNumber((String) b);
    }

    if (a instanceof BigDecimal || b instanceof BigDecimal) {
      BigDecimal decimalA = castToDecimal(a);
      BigDecimal decimalB = castToDecimal(b);
      if (decimalB.scale() < decimalA.scale()) {
        decimalB = decimalB.setScale(decimalA.scale());
      }
      try {
        return decimalA.divide(decimalB);
      } catch (ArithmeticException ex) {
        return decimalA.divide(decimalB, BigDecimal.ROUND_HALF_UP);
      }
    }

    if (a instanceof Double || b instanceof Double) {
      Double doubleA = castToDouble(a);
      Double doubleB = castToDouble(b);
      if (doubleA == null || doubleB == null) {
        return null;
      }
      return doubleA / doubleB;
    }

    if (a instanceof Float || b instanceof Float) {
      Float floatA = castToFloat(a);
      Float floatB = castToFloat(b);
      if (floatA == null || floatB == null) {
        return null;
      }
      return floatA / floatB;
    }

    if (a instanceof BigInteger || b instanceof BigInteger) {
      return castToBigInteger(a).divide(castToBigInteger(b));
    }

    if (a instanceof Long || b instanceof Long) {
      Long longA = castToLong(a);
      Long longB = castToLong(b);
      if (longB == 0) {
        if (longA > 0) {
          return Double.POSITIVE_INFINITY;
        } else if (longA < 0) {
          return Double.NEGATIVE_INFINITY;
        } else {
          return Double.NaN;
        }
      }
      return longA / longB;
    }

    if (a instanceof Integer || b instanceof Integer) {
      Integer intA = castToInteger(a);
      Integer intB = castToInteger(b);
      if (intB == 0) {
        if (intA > 0) {
          return Double.POSITIVE_INFINITY;
        } else if (intA < 0) {
          return Double.NEGATIVE_INFINITY;
        } else {
          return Double.NaN;
        }
      }
      return intA / intB;
    }

    if (a instanceof Short || b instanceof Short) {
      return castToShort(a) / castToShort(b);
    }

    if (a instanceof Byte || b instanceof Byte) {
      return castToByte(a) / castToByte(b);
    }

    throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
  }

  public static boolean gt(Object a, Object b) {
    if (a == null || a == EVAL_VALUE_NULL) {
      return false;
    }

    if (b == null || a == EVAL_VALUE_NULL) {
      return true;
    }

    if (a instanceof String || b instanceof String) {
      return castToString(a).compareTo(castToString(b)) > 0;
    }

    if (a instanceof BigDecimal || b instanceof BigDecimal) {
      return castToDecimal(a).compareTo(castToDecimal(b)) > 0;
    }

    if (a instanceof BigInteger || b instanceof BigInteger) {
      return castToBigInteger(a).compareTo(castToBigInteger(b)) > 0;
    }

    if (a instanceof Long || b instanceof Long) {
      return castToLong(a) > castToLong(b);
    }

    if (a instanceof Integer || b instanceof Integer) {
      return castToInteger(a) > castToInteger(b);
    }

    if (a instanceof Short || b instanceof Short) {
      return castToShort(a) > castToShort(b);
    }

    if (a instanceof Byte || b instanceof Byte) {
      return castToByte(a) > castToByte(b);
    }

    if (a instanceof Date || b instanceof Date) {
      Date d1 = castToDate(a);
      Date d2 = castToDate(b);

      if (d1 == d2) {
        return false;
      }

      if (d1 == null) {
        return false;
      }

      if (d2 == null) {
        return true;
      }

      return d1.compareTo(d2) > 0;
    }

    throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
  }

  public static boolean gteq(Object a, Object b) {
    if (eq(a, b)) {
      return true;
    }

    return gt(a, b);
  }

  public static boolean lt(Object a, Object b) {
    if (a == null) {
      return true;
    }

    if (b == null) {
      return false;
    }

    if (a instanceof String || b instanceof String) {
      return (castToString(a)).compareTo(castToString(b)) < 0;
    }

    if (a instanceof BigDecimal || b instanceof BigDecimal) {
      return castToDecimal(a).compareTo(castToDecimal(b)) < 0;
    }

    if (a instanceof BigInteger || b instanceof BigInteger) {
      return castToBigInteger(a).compareTo(castToBigInteger(b)) < 0;
    }

    if (a instanceof Long || b instanceof Long) {
      return castToLong(a) < castToLong(b);
    }

    if (a instanceof Integer || b instanceof Integer) {
      Integer intA = castToInteger(a);
      Integer intB = castToInteger(b);
      return intA < intB;
    }

    if (a instanceof Short || b instanceof Short) {
      return castToShort(a) < castToShort(b);
    }

    if (a instanceof Byte || b instanceof Byte) {
      return castToByte(a) < castToByte(b);
    }

    if (a instanceof Date || b instanceof Date) {
      Date d1 = castToDate(a);
      Date d2 = castToDate(b);

      if (d1 == d2) {
        return false;
      }

      if (d1 == null) {
        return true;
      }

      if (d2 == null) {
        return false;
      }

      return d1.compareTo(d2) < 0;
    }

    throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
  }

  public static boolean lteq(Object a, Object b) {
    if (eq(a, b)) {
      return true;
    }

    return lt(a, b);
  }

  public static boolean eq(Object a, Object b) {
    if (a == b) {
      return true;
    }

    if (a == null || b == null) {
      return false;
    }

    if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
      return false;
    }

    if (a.equals(b)) {
      return true;
    }

    if (a instanceof String || b instanceof String) {
      return castToString(a).equals(castToString(b));
    }

    if (a instanceof BigDecimal || b instanceof BigDecimal) {
      return castToDecimal(a).compareTo(castToDecimal(b)) == 0;
    }

    if (a instanceof BigInteger || b instanceof BigInteger) {
      return castToBigInteger(a).compareTo(castToBigInteger(b)) == 0;
    }

    if (a instanceof Long || b instanceof Long) {
      return castToLong(a).equals(castToLong(b));
    }

    if (a instanceof Integer || b instanceof Integer) {
      Integer inta = castToInteger(a);
      Integer intb = castToInteger(b);
      if (inta == null || intb == null) {
        return false;
      }
      return inta.equals(intb);
    }

    if (a instanceof Short || b instanceof Short) {
      return castToShort(a).equals(castToShort(b));
    }

    if (a instanceof Boolean || b instanceof Boolean) {
      return castToBoolean(a).equals(castToBoolean(b));
    }

    if (a instanceof Byte || b instanceof Byte) {
      return castToByte(a).equals(castToByte(b));
    }

    if (a instanceof Date || b instanceof Date) {
      Date d1 = castToDate(a);
      Date d2 = castToDate(b);

      if (d1 == d2) {
        return true;
      }

      if (d1 == null || d2 == null) {
        return false;
      }

      return d1.equals(d2);
    }

    throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
  }

  public static Object add(Object a, Object b) {
    if (a == null) {
      return b;
    }

    if (b == null) {
      return a;
    }

    if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
      return EVAL_VALUE_NULL;
    }

    if (a instanceof String && !(b instanceof String)) {
      a = castToNumber((String) a);
    }

    if (b instanceof String && !(a instanceof String)) {
      b = castToNumber((String) b);
    }

    if (a instanceof BigDecimal || b instanceof BigDecimal) {
      return castToDecimal(a).add(castToDecimal(b));
    }

    if (a instanceof BigInteger || b instanceof BigInteger) {
      return castToBigInteger(a).add(castToBigInteger(b));
    }

    if (a instanceof Double || b instanceof Double) {
      return castToDouble(a) + castToDouble(b);
    }

    if (a instanceof Float || b instanceof Float) {
      return castToFloat(a) + castToFloat(b);
    }

    if (a instanceof Long || b instanceof Long) {
      return castToLong(a) + castToLong(b);
    }

    if (a instanceof Integer || b instanceof Integer) {
      return castToInteger(a) + castToInteger(b);
    }

    if (a instanceof Short || b instanceof Short) {
      return castToShort(a) + castToShort(b);
    }

    if (a instanceof Boolean || b instanceof Boolean) {
      int aI = 0, bI = 0;
      if (castToBoolean(a)) {
        aI = 1;
      }
      if (castToBoolean(b)) {
        bI = 1;
      }
      return aI + bI;
    }

    if (a instanceof Byte || b instanceof Byte) {
      return castToByte(a) + castToByte(b);
    }

    if (a instanceof String && b instanceof String) {
      return castToString(a) + castToString(b);
    }

    throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
  }

  public static Object sub(Object a, Object b) {
    if (a == null) {
      return null;
    }

    if (b == null) {
      return a;
    }

    if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
      return EVAL_VALUE_NULL;
    }

    if (a instanceof Date || b instanceof Date) {
      return SQLEvalVisitor.EVAL_ERROR;
    }

    if (a instanceof String) {
      a = castToNumber((String) a);
    }

    if (b instanceof String) {
      b = castToNumber((String) b);
    }

    if (a instanceof BigDecimal || b instanceof BigDecimal) {
      return castToDecimal(a).subtract(castToDecimal(b));
    }

    if (a instanceof BigInteger || b instanceof BigInteger) {
      return castToBigInteger(a).subtract(castToBigInteger(b));
    }

    if (a instanceof Double || b instanceof Double) {
      return castToDouble(a) - castToDouble(b);
    }

    if (a instanceof Float || b instanceof Float) {
      return castToFloat(a) - castToFloat(b);
    }

    if (a instanceof Long || b instanceof Long) {
      return castToLong(a) - castToLong(b);
    }

    if (a instanceof Integer || b instanceof Integer) {
      return castToInteger(a) - castToInteger(b);
    }

    if (a instanceof Short || b instanceof Short) {
      return castToShort(a) - castToShort(b);
    }

    if (a instanceof Boolean || b instanceof Boolean) {
      int aI = 0, bI = 0;
      if (castToBoolean(a)) {
        aI = 1;
      }
      if (castToBoolean(b)) {
        bI = 1;
      }
      return aI - bI;
    }

    if (a instanceof Byte || b instanceof Byte) {
      return castToByte(a) - castToByte(b);
    }

    // return SQLEvalVisitor.EVAL_ERROR;
    throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
  }

  public static Object multi(Object a, Object b) {
    if (a == null || b == null) {
      return null;
    }

    if (a instanceof String) {
      a = castToNumber((String) a);
    }

    if (b instanceof String) {
      b = castToNumber((String) b);
    }

    if (a instanceof BigDecimal || b instanceof BigDecimal) {
      return castToDecimal(a).multiply(castToDecimal(b));
    }

    if (a instanceof BigInteger || b instanceof BigInteger) {
      return castToBigInteger(a).multiply(castToBigInteger(b));
    }

    if (a instanceof Double || b instanceof Double) {
      return castToDouble(a) * castToDouble(b);
    }

    if (a instanceof Float || b instanceof Float) {
      return castToFloat(a) * castToFloat(b);
    }

    if (a instanceof Long || b instanceof Long) {
      return castToLong(a) * castToLong(b);
    }

    if (a instanceof Integer || b instanceof Integer) {
      return castToInteger(a) * castToInteger(b);
    }

    if (a instanceof Short || b instanceof Short) {
      Short shortA = castToShort(a);
      Short shortB = castToShort(b);

      if (shortA == null || shortB == null) {
        return null;
      }

      return shortA * shortB;
    }

    if (a instanceof Byte || b instanceof Byte) {
      return castToByte(a) * castToByte(b);
    }

    throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
  }

  public static boolean like(String input, String pattern) {
    if (pattern == null) {
      throw new IllegalArgumentException("pattern is null");
    }

    StringBuilder regexprBuilder = new StringBuilder(pattern.length() + 4);

    final int STAT_NOTSET = 0;
    final int STAT_RANGE = 1;
    final int STAT_LITERAL = 2;

    int stat = STAT_NOTSET;

    int blockStart = -1;
    for (int i = 0; i < pattern.length(); ++i) {
      char ch = pattern.charAt(i);

      if (stat == STAT_LITERAL //
          && (ch == '%' || ch == '_' || ch == '[')) {
        String block = pattern.substring(blockStart, i);
        regexprBuilder.append("\\Q");
        regexprBuilder.append(block);
        regexprBuilder.append("\\E");
        blockStart = -1;
        stat = STAT_NOTSET;
      }

      if (ch == '%') {
        regexprBuilder.append(".*");
      } else if (ch == '_') {
        regexprBuilder.append('.');
      } else if (ch == '[') {
        if (stat == STAT_RANGE) {
          throw new IllegalArgumentException("illegal pattern : " + pattern);
        }
        stat = STAT_RANGE;
        blockStart = i;
      } else if (ch == ']') {
        if (stat != STAT_RANGE) {
          throw new IllegalArgumentException("illegal pattern : " + pattern);
        }
        String block = pattern.substring(blockStart, i + 1);
        regexprBuilder.append(block);

        blockStart = -1;
      } else {
        if (stat == STAT_NOTSET) {
          stat = STAT_LITERAL;
          blockStart = i;
        }

        if (stat == STAT_LITERAL && i == pattern.length() - 1) {
          String block = pattern.substring(blockStart, i + 1);
          regexprBuilder.append("\\Q");
          regexprBuilder.append(block);
          regexprBuilder.append("\\E");
        }
      }
    }
    if ("%".equals(pattern) || "%%".equals(pattern)) {
      return true;
    }

    String regexpr = regexprBuilder.toString();
    return Pattern.matches(regexpr, input);
  }

  public static boolean visit(SQLEvalVisitor visitor, SQLIdentifierExpr x) {
    x.putAttribute(EVAL_EXPR, x);
    return false;
  }
}
